Bloom Filter简介。
 
布隆过滤器 是用来判断一个元素是否在集合中的数据结构,它使用一系列Hash函数和一个位数组来判断。
首先需要准备一个长度为m的位数组,每一位初始化为0,然后在存元素的时候通过k个Hash函数分别将元素映射到数组的k个位置,并设置为1。之后在判断某一元素是否存在于集合中就只需要判断通过k个Hash函数分别将元素映射到数组的k个位是否都为1,如果不是,那么该元素一定不在集合中,如果是,那么元素有一定概率在集合中,这个概率是多少,取决于m,k和存储在集合中的元素个数n。
上图将x,y,z存入集合,将对应的位设为1,然后查询w是否在集合中,由于通过3个Hash函数计算出来的对应位置有一位不为1,则表示w不在集合中。
概率如何计算:Probability of false positives 这里讲的比较详细了。
其实我们可以发现如果m越长,n越少,k越多那么误判的几率就越低。
k的选择需要考虑m和n的值,可以详看上面的链接,这里直接给出k取$(\frac mn)ln2$为最佳。
Bloom Filter的时间和空间优势还是挺明显的,相比较HashSet等其他方式来判断是否在集合中,空间上由于Bloom Filter并不存储数据本身,而只用了一个位数组,这能极大的节省空间,时间上面需要通过k个Hash函数,可以认为是常数的复杂度。缺点就是会出现误判,也不支持删除操作。
虽然Bloom Filter通过合理的选择m和k能有效降低误判,但是还是免不了出现误判,所以Bloom Filter适合哪些不要求百分百正确的场景,比如垃圾邮件,黑名单等等。
代码如下(Github地址 ):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 package  io.github.ztmark;import  com.google.common.collect.ImmutableList;import  com.google.common.hash.HashFunction;import  com.google.common.hash.Hashing;import  java.util.ArrayList;import  java.util.BitSet;import  java.util.List;import  java.util.Random;public  class  BloomFilter <T >  {    private  final  BitSet bitSet;     private  final  int  bitSize;     private  final  int  expectedNumberOfElements;     private  final  ImmutableList<HashFunction> hashFunctions;     private  final  int  k;     private  int  addedNumberOfElements;     public  BloomFilter (int  bitSize, int  expectedNumberOfElements)   {         if  (bitSize <= 0  || expectedNumberOfElements <= 0 ) {             throw  new  RuntimeException("wrong parameters bitSize and expectedNumberOfElements should greater than zero." );         }         this .bitSize = bitSize;         bitSet = new  BitSet(bitSize);         this .expectedNumberOfElements = expectedNumberOfElements;                  k = (int ) Math.round(1.0  * bitSize / expectedNumberOfElements * Math.log(2 ));         hashFunctions = HashFunctionGenerator.generateKHashFunctions(k);         addedNumberOfElements = 0 ;     }     public  void  add (T elem)   {         for  (HashFunction function : hashFunctions) {             bitSet.set(Math.abs(function.hashInt(elem.hashCode()).asInt() / bitSize));         }         addedNumberOfElements++;     }     public  boolean  contains (T elem)   {         for  (HashFunction function : hashFunctions) {             if  (!bitSet.get(Math.abs(function.hashInt(elem.hashCode()).asInt() / bitSize))) {                 return  false ;             }         }         return  true ;     }     public  double  getFalsePositiveProbability ()   {                  return  Math.pow((1  - Math.exp(-k * 1.0  * addedNumberOfElements / bitSize)), k);     }     public  int  getAddedNumberOfElements ()   {         return  addedNumberOfElements;     }     public  int  getBitSize ()   {         return  bitSize;     }     public  int  getExpectedNumberOfElements ()   {         return  expectedNumberOfElements;     }     public  int  getNumberOfHashFunction ()   {         return  k;     }     private  static  final  class  HashFunctionGenerator   {         public  static  HashFunction generateAHashFunction ()   {             return  Hashing.murmur3_128(new  Random(System.currentTimeMillis()).nextInt());         }         public  static  ImmutableList<HashFunction> generateKHashFunctions (int  k)   {             Random random = new  Random(System.currentTimeMillis());             List<HashFunction> hashFunctions = new  ArrayList<HashFunction>();             for  (int  i = 0 ; i < k; i++) {                 hashFunctions.add(Hashing.murmur3_128(random.nextInt()));             }             return  ImmutableList.copyOf(hashFunctions);         }     } }